/*
 *  Copyright 2010 Susanta Tewari.
 *  This file is part of genomemap.
 * 
 *  genomemap is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  genomemap is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with genomemap.  If not, see <http://www.gnu.org/licenses/>.
 */
package genomemap.queries;

import genomemap.data.AssignmentManager;
import genomemap.io.DataIO;
import genomemap.map.ContigMap;
import genomemap.map.FullContigMap;
import genomemap.map.MapException;
import genomemap.map.ProbeMap;
import genomemap.map.RepeatsDistMap;
import genomemap.map.GeneMap;
import genomemap.map.MinimalContigMap;
import genomemap.util.Util;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Generates results used in the paper PlosBiology. The topic of research in this paper is
 * integration of physical and genetic maps for Neurospora crassa genome.
 *
 * @version 1.0 Mar 24, 2010
 * @author Susanta Tewari
 */
public class PlosBiology extends Query {

    private List<BestMaps> bestMapsList;

    private List<Maps> mapsList;

    private RepeatsMapDO repeatsMap;

    private List<GenomeSizeEstimate> genomeSizeList;

    /**
     * <ol>
     *
     * <li> Validates query.properties for certain properties. For details look
     * {@link PlosBiology#validate()}.
     *
     * <li> Loads best genetic maps, physical maps and integrated maps from query.properties into
     * <code>BestMaps</code>
     * 
     * </ol>
     */
    public PlosBiology() {
    }

    /**
     * We will be using data.jar for the source of data. It will check for the
     * following validation rules.
     * <ol>
     * <li> For the linkage groups mentioned in linkage.groups there must be a corresponding entry
     * each for physicalmap.ch*, geneticmap.ch* and integratedmap.ch*.
     * </ol>
     *
     * @throws RuntimeException if validation error occurs
     */
    private void validate() throws RuntimeException {

        for (Integer linkageGroup : DataIO.getInstance().getLinkageGroups()) {

            String geneticMapProp = QueryRunner.getInstance().getProperty("geneticmap.ch"
                    + linkageGroup);
            String physicalMapProp = QueryRunner.getInstance().getProperty("physicalmap.ch"
                    + linkageGroup);
            String integratedMapProp = QueryRunner.getInstance().getProperty("integratedmap.ch"
                    + linkageGroup);

            if (geneticMapProp == null || geneticMapProp.length() == 0) throw new RuntimeException("property: "
                        + "geneticmap.ch" + linkageGroup + " is not properly set");

            if (physicalMapProp == null || physicalMapProp.length() == 0) throw new RuntimeException("property: "
                        + "physicalmap.ch" + linkageGroup + " is not properly set");

            if (integratedMapProp == null || integratedMapProp.length() == 0) throw new RuntimeException("property: "
                        + "integratedmap.ch" + linkageGroup + " is not properly set");

        }

    }

    private void build() {

        validate();

        QueryRunner runner = QueryRunner.getInstance();
        List<Integer> linkageGroups = DataIO.getInstance().getLinkageGroups();

        // configure output dir
        String outputDir = QueryRunner.getInstance().getProperty(getName()
                + QueryRunner.QUERY_OUTPUT_DIR_SUFFIX);
        String fileSeparator = System.getProperty("file.separator");

        bestMapsList = new ArrayList<BestMaps>();
        mapsList = new ArrayList<Maps>();
        genomeSizeList = new ArrayList<GenomeSizeEstimate>();

        for (Integer linkageGroup : linkageGroups) {

            // <editor-fold defaultstate="collapsed" desc="Load Best Maps">
            GeneMap bestGeneticMap = new GeneMap(linkageGroup, Arrays.asList(
                    runner.getProperty("geneticmap.ch" + linkageGroup).split(",")));

            ProbeMap bestPhysicalMap = new ProbeMap(linkageGroup, Arrays.asList(
                    runner.getProperty("physicalmap.ch" + linkageGroup).split(",")));

            ProbeMap bestIntegratedMap = new ProbeMap(linkageGroup, Arrays.asList(
                    runner.getProperty("integratedmap.ch" + linkageGroup).split(",")));
            // </editor-fold>

            // <editor-fold defaultstate="collapsed" desc="Genome Size Estimate">
            // genome size
            genomeSizeList.add(new GenomeSizeEstimate(bestPhysicalMap.estimateChromosomeSize(),
                    bestIntegratedMap.estimateChromosomeSize()));

            // </editor-fold>

            // <editor-fold defaultstate="collapsed" desc="Create Maps">
            // load best maps from properties file

            AssignmentManager asm = AssignmentManager.getInstance(linkageGroup);

            // create gene alignment maps
            List<genomemap.map.GeneMap> gip_gene_maps = new ArrayList<genomemap.map.GeneMap>();
            gip_gene_maps.add(new GeneMap(linkageGroup, bestGeneticMap.getGenes(), "Genetic Map"));
            gip_gene_maps.add(new GeneMap(linkageGroup,
                    asm.getModifiedGeneMap(bestGeneticMap.getGenes(), bestIntegratedMap.getProbes()),
                    "Integrated Map"));
            gip_gene_maps.add(new GeneMap(linkageGroup,
                    asm.getModifiedGeneMap(bestGeneticMap.getGenes(), bestPhysicalMap.getProbes()),
                    "Physical Map"));

            Logger.getLogger(getClass().getName()).log(Level.INFO,
                    "Building alignment map for genes for linkage group: " + linkageGroup);
            AlignmentMapDO gip_gene_alignment_map = null;
            try {

                gip_gene_alignment_map =
                        new AlignmentMapDO(new genomemap.map.AlignmentMap(gip_gene_maps, true),
                        outputDir + fileSeparator + "gip_alignment_map_4genes_" + linkageGroup
                        + ".jpeg");
                gip_gene_alignment_map.save();

            } catch (MapException ex) {
                Logger.getLogger(PlosBiology.class.getName()).log(Level.SEVERE,
                        "alignment map for genes could not be created for linkage group: "
                        + linkageGroup, ex);
            }


            // probe alignment maps
            List<genomemap.map.ProbeMap> gis_probe_maps = new ArrayList<genomemap.map.ProbeMap>();
            gis_probe_maps.add(new ProbeMap(linkageGroup,
                    asm.getModifiedProbeMap(bestIntegratedMap.getProbes(), bestGeneticMap.getGenes()),
                    "Genetic Map"));
            gis_probe_maps.add(new ProbeMap(linkageGroup, bestIntegratedMap.getProbes(),
                    "Integrated Map"));
            gis_probe_maps.add(new ProbeMap(linkageGroup, bestPhysicalMap.getProbes(),
                    "Physical Map"));

            Logger.getLogger(getClass().getName()).log(Level.INFO,
                    "Building alignment map for probes for linkage group: " + linkageGroup);
            AlignmentMapDO gis_probe_alignment_map = null;
            try {

                gis_probe_alignment_map =
                        new AlignmentMapDO(new genomemap.map.AlignmentMap(gis_probe_maps, true),
                        outputDir + fileSeparator + "gis_alignment_map_4probes_" + linkageGroup
                        + ".jpeg");
                gis_probe_alignment_map.save();

            } catch (MapException ex) {
                Logger.getLogger(PlosBiology.class.getName()).log(Level.SEVERE,
                        "alignment map for probes could not be created for linkage group: "
                        + linkageGroup, ex);
            }

            // full contig map
            Logger.getLogger(getClass().getName()).log(Level.INFO,
                    "Building full contig map for linkage group: " + linkageGroup);
            ContigMapDO fullContigMapDO = new ContigMapDO(new FullContigMap(bestIntegratedMap),
                    outputDir + fileSeparator + "fullContigMap_" + linkageGroup
                    + ".txt");
            fullContigMapDO.save();


            // minimal contig map
            Logger.getLogger(getClass().getName()).log(Level.INFO,
                    "Building minimal contig map for linkage group: " + linkageGroup);
            ContigMapDO minimalContigMap = new ContigMapDO(new MinimalContigMap(bestIntegratedMap),
                    outputDir + fileSeparator + "minimalContigMap_" + linkageGroup
                    + ".txt");
            minimalContigMap.save();

            mapsList.add(new Maps(linkageGroup, gip_gene_alignment_map, gis_probe_alignment_map,
                    fullContigMapDO, minimalContigMap));

            bestMapsList.add(new BestMaps(linkageGroup, bestGeneticMap, bestPhysicalMap,
                    bestIntegratedMap));

            // </editor-fold>

        }

        // <editor-fold defaultstate="collapsed" desc="Repeats Dist">
        // create repeats dist
        Logger.getLogger(getClass().getName()).log(Level.INFO,
                "Building repeats distribution map for the entire genome.");
        RepeatsDistMap repeatsDistMap = new RepeatsDistMap("Neurospora Repeats Distribution Map",
                getBestIntegratedMaps());
        repeatsMap =
                new RepeatsMapDO(repeatsDistMap, outputDir + fileSeparator + "rep_dist_map.jpg");
        repeatsMap.save();// </editor-fold>

    }

    // <editor-fold defaultstate="collapsed" desc="Property Getters">
    public List<BestMaps> getBestMapsList() {
        return bestMapsList;
    }

    public List<Maps> getMapsList() {
        return mapsList;
    }

    public List<GenomeSizeEstimate> getGenomeSizeList() {
        return genomeSizeList;
    }

    public RepeatsMapDO getRepeatsMap() {
        return repeatsMap;
    }// </editor-fold>

    public Object getDataModel() {

        Map root = new HashMap();

        build();

        root.put("plosBiology", this);

        return root;

    }

    private List<ProbeMap> getBestIntegratedMaps() {

        List<ProbeMap> result = new ArrayList<ProbeMap>();

        for (BestMaps bestMaps : bestMapsList)
            result.add(bestMaps.getBestIntegratedMap());

        return result;
    }

    public class Maps {

        private Integer linkageGroup;

        private AlignmentMapDO gipAlignmentMapForGenes;

        private AlignmentMapDO gisAlignmentMapForProbes;

        private ContigMapDO fullContigMap;

        private ContigMapDO minimalContigMap;

        public Maps(Integer linkageGroup, AlignmentMapDO gipAlignmentMapForGenes,
                AlignmentMapDO gisAlignmentMapForProbes, ContigMapDO fullContigMap,
                ContigMapDO minimalContigMap) {

            this.linkageGroup = linkageGroup;
            this.gipAlignmentMapForGenes = gipAlignmentMapForGenes;
            this.gisAlignmentMapForProbes = gisAlignmentMapForProbes;
            this.fullContigMap = fullContigMap;
            this.minimalContigMap = minimalContigMap;
        }

        public Integer getLinkageGroup() {
            return linkageGroup;
        }

        public AlignmentMapDO getGipAlignmentMapForGenes() {
            return gipAlignmentMapForGenes;
        }

        public AlignmentMapDO getGisAlignmentMapForProbes() {
            return gisAlignmentMapForProbes;
        }

        public ContigMapDO getFullContigMap() {
            return fullContigMap;
        }

        public ContigMapDO getMinimalContigMap() {
            return minimalContigMap;
        }

    }

    /**
     *
     */
    public class ContigMapDO {

        private ContigMap map;

        private String file;

        private ContigMapDO(ContigMap map, String file) {
            this.map = map;
            this.file = file;
        }

        public String getFile() {
            return new File(file).getName();
        }

        public Double getAvgContigSize() {
            return map.getAvgContigSize();
        }

        public Double getAvgClonesPerContig() {
            return map.getAvgClonesPerContig();
        }

        public void save() {

            Util.IO.writeToFile(new File(file), map.toString());
        }

    }

    public class AlignmentMapDO {

        private genomemap.map.AlignmentMap map;

        private String file;

        private AlignmentMapDO(genomemap.map.AlignmentMap map, String file) {
            this.map = map;
            this.file = file;
        }

        public String getFile() {
            return new File(file).getName();
        }

        public List<Integer> getCrossCounts() {

            return map.getCrossCounts();
        }

        public void save() {
            Util.saveImage(new File(file), Util.IMAGE_FORMAT_JPEG, map, map.getWidth(),
                    map.getHeight());
        }

    }

    public class RepeatsMapDO {

        private RepeatsDistMap map;

        private String file;

        private RepeatsMapDO(RepeatsDistMap repeatsDistMap, String file) {
            this.map = repeatsDistMap;
            this.file = file;
        }

        public String getFile() {
            return new File(file).getName();
        }

        public void save() {
            Util.saveImage(new File(file), Util.IMAGE_FORMAT_JPEG, map, map.getWidth(),
                    map.getHeight());
        }

    }

    public class GenomeSizeEstimate {

        /**
         * estimate of genome length in bases using best physical map
         */
        private Double estimateByPhysicalMap;

        /**
         * estimate of genome length in bases using best integrated map
         */
        private Double estimateByIntegratedMap;

        private GenomeSizeEstimate(Double estimateByPhysicalMap, Double estimateByIntegratedMap) {

            this.estimateByPhysicalMap = estimateByPhysicalMap;
            this.estimateByIntegratedMap = estimateByIntegratedMap;
        }

        public Double getEstimateByIntegratedMap() {
            return estimateByIntegratedMap;
        }

        public Double getEstimateByPhysicalMap() {
            return estimateByPhysicalMap;
        }

    }
}
